<template>
  <ClientOnly>
    <div class="image-zoom-wrapper">
      <div class="slot-wrapper" ref="slotRef">
        <slot></slot>
      </div>
      <div class="image-wrapper" :class="{
        'is-chart': isChartSvg,
        'too-small': isTooSmall,
      }" @click="preview">
        <div class="zoom-wrapper">
          <OIcon>
            <IconZoomOut></IconZoomOut>
          </OIcon>
        </div>
        <div ref="wrapperRef" :class="{
          'image-dom': true,
          'loaded': loaded,
          'loaded-svg': loadedSvg,
          'is-chart': isChartSvg,
        }" :style="{
          '--width': width,
          '--height': height,
        }">
          <div ref="svgRef" class="svg-dom"></div>
          <img :src="imgSrc" alt="">
        </div>
        <div class="image-mask"></div>
      </div>
      <div class="image-zoom-modal" v-if="visible" ref="modalRef" @wheel.prevent="scroll" @click="closePreview">
        <div class="image-zoom-body" :style="{
          '--width': finalWidth,
          '--height': finalHeight,
        }" @click="clickBody">
          <div class="zoom-wrapper" @click="closePreview">
            <OIcon>
              <IconZoomIn></IconZoomIn>
            </OIcon>
          </div>
          <div class="img-wrapper">
            <img :src="imgSrc" alt="" :style="{
              transform: `scale(${imgScale}) translate(${translateX}px, ${translateY}px)`,
              cursor: imgCursor,
            }" @mousedown.prevent="startDrag" @touchstart.prevent="startDragOnMobile" @mouseup.prevent="stopDrag"
                 @touchend.prevent="stopDragOnMobile" @mousemove.prevent="onDrag" @touchmove.prevent="onDragOnMobile"
                 @wheel.prevent="scrollImg" />
          </div>
        </div>
      </div>
    </div>
  </ClientOnly>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { isClient, OIcon } from '@opensig/opendesign';
import IconZoomOut from '~icons/app/icon-zoom-out.svg';
import IconZoomIn from '~icons/app/icon-zoom-in.svg';

const slotRef = ref(null); // slot dom
const svgRef = ref(); // svg dom
const loaded = ref(false); // 图片是否加载完成
const loadedSvg = ref(false); // svg 是否加载完成
const clientWidth = ref(0); // 客户端宽度
const clientHeight = ref(0); // 客户端高度
const wrapperRef = ref(); // 图片外层 dom
const imgSrc = ref<string | null>(null); // 图片 src
const visible = ref(false); // 图片预览是否可见
const modalRef = ref(null); // 图片预览 dom
const isChartSvg = ref(false); // 是否是图表图片
const isTooSmall = ref(false); // 图片是否过小
const width = ref(0); // 图片原始宽度
const height = ref(0); // 图片原始高度
const finalWidth = computed(() => Math.min(clientWidth.value - 64, width.value * scale.value));  // 64px margin
const finalHeight = computed(() => Math.min(clientHeight.value - 64, height.value * scale.value)); // 64px margin

const scale = ref(1.5); // 弹窗缩放比例

const isMaxScaled = computed(() => scale.value >= maxScale.value); // 是否最大缩放
// 920 600 300 60
// 预设宽度列表（按升序排列）
const PRESET_WIDTHS = [60, 300, 600, 920];

// 获取图片尺寸
const getClosestWidth = (src: string) => {
  const img = new Image();
  img.src = src;
  img.onload = () => {
    const naturalWidth = img.naturalWidth;
    const naturalHeight = img.naturalHeight;
    const closestWidth = width.value = PRESET_WIDTHS.reduce((prev, curr) =>
      Math.abs(curr - naturalWidth) < Math.abs(prev - naturalWidth) ? curr : prev,
    );
    height.value = closestWidth / (naturalWidth / naturalHeight);
    isTooSmall.value = width.value <= 60;
  };
};

// -------------------- 图片预览 --------------------

// 如果图片是svg，则直接渲染svg dom
const clockId = ref(null); // 定时器id
const getImgFromSlot = async () => {
  const slotSvg = slotRef.value?.querySelector('svg');

  let src = '';
  const slotEl = slotRef.value?.querySelector('img');
  if (!slotEl && !slotSvg) {
    clockId.value = setTimeout(() => {
      getImgFromSlot();
    }, 200);
    return;
  } else {
    if (clockId.value) {
      clearTimeout(clockId.value);
    }
    if (slotSvg) {
      // 如果是svg（图表）,直接渲染
      loadedSvg.value = true;
      loaded.value = true;
      isChartSvg.value = true;
      // slotSvg.setAttribute('style', 'max-width: 920px;');
      if (svgRef.value) {
        svgRef.value.innerHTML = slotSvg.outerHTML;
      }
      return;
    } else {
      src = slotEl.src;
    }
  }
  if (!src) {
    return;
  }
  getClosestWidth(src);
  if (!src.endsWith('.svg')) {
    // 如果不是svg，则直接渲染img dom
    loaded.value = true;
    loadedSvg.value = false;
    imgSrc.value = src;
    return;
  }
  try {
    const response = await fetch(src);
    if (!response.ok) {
      throw new Error('HTTP error');
    }
    const svgText = await response.text();
    const parser = new DOMParser();
    const svgDoc = parser.parseFromString(svgText.replace(/([\w-]+:\s*)light-dark\(\s*((?:#\w+|rgba?\([^)]+\)|var\([^)]+\)|[^,)]+))\s*,.*?\)+/gi, '$1$2'), 'image/svg+xml');

    // 检查解析错误
    const errorNode = svgDoc.querySelector('parsererror');
    if (errorNode) {
      throw new Error('SVG 解析错误');
    }

    const svgElement = svgDoc.documentElement;
    svgElement.setAttribute('id', `dynamic-svg-${ Math.random() }`);
    svgElement.setAttribute('class', 'dynamic-svg');
    // 自动清理无用属性
    svgElement.removeAttribute('xmlns:a');
    // 移除脚本（安全考虑）
    svgElement.querySelectorAll('script').forEach(script => script.remove());
    if (svgRef.value) {
      svgRef.value.innerHTML = svgElement.outerHTML;
    }
    loadedSvg.value = true;
  } finally {
    loaded.value = true;
  }
};

const getClientSize = () => {
  if (isClient) {
    clientWidth.value = document.documentElement.clientWidth;
    clientHeight.value = document.documentElement.clientHeight;
  }
};


// 根据屏幕尺寸计算弹窗的最大缩放比例
const maxScale = computed(() => {
  if (isClient && width.value && height.value) {
    return Math.min(
      Number(((clientWidth.value - 64) / width.value).toFixed(2)),
      Number(((clientHeight.value - 64) / height.value).toFixed(2)),
    );
  }
  return 3;
});
// 鼠标滚动调整弹窗大小
const scroll = (event: WheelEvent) => {
  if (imgScale.value > 1) {
    return; // 如果图片已经放大，则不允许缩放
  }
  if (isDragging.value) {
    return;
  }
  const delta = event.deltaY > 0 ? -1 : 1;
  scale.value = Math.min(
    Math.max(scale.value + delta * 0.1, 1),
    maxScale.value,
  );
  translateToOrigin();
};
// 点击图片放大，弹窗打开
const preview = () => {
  if (!loaded.value) {
    return;
  }
  if (!loadedSvg.value) {
    // 普通图片
    const img = wrapperRef.value.querySelector('img');
    imgSrc.value = img.src;
  } else {
    // 图表图片
    const svg = wrapperRef.value.querySelector('svg');
    const svgString = new XMLSerializer().serializeToString(svg);
    const blob = new Blob([svgString], { type: 'image/svg+xml' });
    imgSrc.value = URL.createObjectURL(blob);
    if (!width.value || !height.value) {
      width.value = svg.clientWidth;
      height.value = svg.clientHeight;
    }
  }
  if (!width.value || !height.value) {
    return;
  }
  visible.value = true;
  document.addEventListener('keydown', handleKeyDown);
  getClientSize();

};
const clickBody = (e: MouseEvent) => {
  e.preventDefault();
  e.stopPropagation();
};
// 关闭预览
const closePreview = () => {
  if (!isDragging.value) {
    visible.value = false;
  }
};
const handleKeyDown = (e: KeyboardEvent) => {
  if (e.code === 'Escape') {
    closePreview();
  }
};

// -------------------- 图片拖拽 --------------------
const imgScale = ref(1); // 图片缩放比例
const maxImgScale = ref(3); // 图片最大缩放比例
const translateX = ref(0); // 图片 x 轴偏移量
const translateY = ref(0); // 图片 y 轴偏移量
const imgCursor = computed(() => {
  if (isDragging.value) {
    return 'grabbing';
  }
  if (isMaxScaled.value) {
    return 'grab';
  }
  return 'auto';
});
const scrollImg = (event: WheelEvent) => {
  if (!isMaxScaled.value) {
    return;
  }
  const delta = event.deltaY > 0 ? -1 : 1;
  imgScale.value = Math.max(imgScale.value + delta * 0.1, 1);
  if (imgScale.value > maxImgScale.value) {
    imgScale.value = maxImgScale.value;
  }
  if (imgScale.value < 1) {
    imgScale.value = 1;
  }
};


const isDragging = ref(false);
const dragStartX = ref(0);
const dragStartY = ref(0);
const startTranslateX = ref(0);
const startTranslateY = ref(0);

const startDragOnMobile = (e: TouchEvent) => {
  const event = e.touches[0] || e.changedTouches[0];
  startDrag(event as unknown as MouseEvent);
};
// 鼠标按下时开始拖拽
const startDrag = (event: MouseEvent) => {
  if (!isMaxScaled.value) {
    return; // 仅最大缩放时允许拖拽
  }
  isDragging.value = true;
  dragStartX.value = event.clientX;
  dragStartY.value = event.clientY;
  startTranslateX.value = translateX.value;
  startTranslateY.value = translateY.value;

  // 绑定全局事件（防止鼠标移出元素外）
  window.addEventListener('mousemove', onDrag);
  window.addEventListener('mouseup', stopDrag);
};

const stopDragOnMobile = () => {
  stopDrag();
};
// 鼠标抬起时停止拖拽
const stopDrag = () => {
  setTimeout(() => {
    isDragging.value = false;
  }, 0);
  // 移除全局事件
  window.removeEventListener('mousemove', onDrag);
  window.removeEventListener('mouseup', stopDrag);
};

const maxTranslateX = computed(() => (finalWidth.value * (imgScale.value - 1)) / 2 / imgScale.value);
const maxTranslateY = computed(() => (finalHeight.value * (imgScale.value - 1)) / 2 / imgScale.value);

const onDragOnMobile = (e: TouchEvent) => {
  const event = e.touches[0] || e.changedTouches[0];
  onDrag(event as unknown as MouseEvent);
};
// 处理拖拽事件
const onDrag = (event: MouseEvent) => {
  if (!isDragging.value) {
    return;
  }
  const dx = event.clientX - dragStartX.value;
  const dy = event.clientY - dragStartY.value;
  translateX.value = startTranslateX.value + dx / imgScale.value;
  translateY.value = startTranslateY.value + dy / imgScale.value;
};

// 限制拖拽范围
watch(() => [translateX.value, translateY.value, maxTranslateX.value, maxTranslateY.value], () => {
  translateX.value = Math.max(Math.min(translateX.value, maxTranslateX.value), -maxTranslateX.value);
  translateY.value = Math.max(Math.min(translateY.value, maxTranslateY.value), -maxTranslateY.value);
});

// 重置图片位置到原点
const translateToOrigin = () => {
  translateX.value = 0;
  translateY.value = 0;
};
onMounted(() => {
  if (isClient) {
    getClientSize();
    window.addEventListener('resize', getClientSize);
    getImgFromSlot();
  }
});

onUnmounted(() => {
  document.removeEventListener('keydown', handleKeyDown);
  document.removeEventListener('resize', getClientSize);
});
</script>

<style lang="scss" scoped>
.zoom-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  margin-left: auto;
  position: absolute;
  z-index: 2;
  top: 16px;
  right: 16px;
  width: 32px;
  height: 32px;
  background-color: rgba(var(--o-black), .4);
  border-radius: 4px;
  opacity: 0;
  transition: opacity var(--o-duration-s) var(--o-easing-standard);

  .o-icon {
    font-size: 24px;
    color: #fff;
  }

  svg, img {
    padding: 0;
    max-width: 100%;
  }
}

.slot-wrapper {
  height: 0;
  overflow: hidden;
  opacity: 0;
}

.image-zoom-wrapper {
  padding: 8px;

  .image-wrapper {
    overflow: hidden;
    display: flex;
    flex-direction: column;
    position: relative;
    border-radius: 4px;
    cursor: pointer;
    width: fit-content;

    .image-dom {
      img {
        width: calc(var(--width) * 1px);
        object-fit: contain;
        border-radius: 4px;
      }

      :deep(.svg-dom) {
        svg {
          width: calc(var(--width) * 1px);
          object-fit: contain;
          border-radius: 4px;
        }
      }

      &.loaded-svg {
        img {
          display: none;
        }
      }
    }

    &.is-chart {
      width: 100%;
      max-width: 920px;

      .image-dom {
        :deep(.svg-dom) {
          svg {
            width: auto;
          }
        }
      }
    }

    .image-mask {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: rgba(var(--o-black), .2);
      z-index: 1;
      border-radius: 4px;
      opacity: 0;
      transition: opacity var(--o-duration-s) var(--o-easing-standard);
      border: 1px solid rgba(var(--o-black), .1);
    }

    &:hover {
      .zoom-wrapper, .image-mask {
        opacity: 1;
      }
    }

    &.too-small {
      cursor: auto;

      &:hover {
        .zoom-wrapper, .zoom-wrapper, .image-mask {
          display: none;
        }
      }
    }
  }
}

.image-zoom-modal {
  &::before {
    content: '';
    background-color: var(--o-color-mask1);
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
    pointer-events: auto;
  }

  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 200;

  .image-zoom-body {
    display: flex;
    flex-direction: column;
    pointer-events: auto;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translateX(-50%) translateY(-50%);
    z-index: 201;
    background-color: var(--o-color-fill2);
    width: calc(var(--width) * 1px);
    height: calc(var(--height) * 1px);
    transition: width 0.2s ease-in-out, height 0.2s ease-in-out;

    .zoom-wrapper {
      opacity: 1;
    }

    .img-wrapper {
      overflow: hidden;
      padding: 0;
      height: 100%;

      img {
        padding: 0;
        width: 100%;
        height: 100%;
        cursor: auto;
        user-select: none;
        transition: transform 0.05s ease-in-out;
        background-color: var(--o-color-fill2);
      }
    }
  }
}
</style>
